// Copyright 2008 Senary
// See credits.txt for details.

// This file is part of Omega VII.

// Omega VII is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Omega VII is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Omega VII.  If not, see <http://www.gnu.org/licenses/>.

#include <map>
#define _USE_MATH_DEFINES
#include <limits>
#include <algorithm>
#include <cstdlib>

#include "GameManager.h"
#include "InputManager.h"
#include "Player.h"
#include "UIManager.h"
#include "MediaManager.h"
#include "Camera.h"
#include "Constants.h"


using namespace std;

GameManager* GameManager::_pInstance = 0;

GameManager::GameManager(void) : _messageBoard() ,ev(0), _pCamera(0),
	_inputManager(0), _unitDecorator(new UnitDecorator)
{
}

GameManager::~GameManager(void)
{
	if (_pInstance != 0) delete _pInstance;
	if (_pCamera != 0) delete _pCamera;
	if (_inputManager != 0) delete _inputManager;
	if (_UIManager != 0) delete _UIManager;
	if (_unitDecorator != 0) delete _unitDecorator;

	// Free the memory of our objects...
	reset();

	// Close all GameStudio stuff
	engine_close();
	ev = 0;
}

//singleton method
GameManager* GameManager::getInstance(void)
{
	if(_pInstance==0) {
		_pInstance = new GameManager;
	}

	return _pInstance;
}

// getter for the level
Level& GameManager::getLevel()
{
	return _level;
}

Camera* GameManager::getCamera() const
{
	return _pCamera;
}

InputManager* GameManager::getInputManager() const
{
	return _inputManager;
}

UIManager* GameManager::getUIManager() const
{
	return _UIManager;
}

//getter for SfxManager
SfxManager & GameManager::getSfxManager()
{
	return _sfxManager;
}


// Returns the object corresponding to a given entity
// If the ENTITY does not exist, returns 0.
SpaceObject* GameManager::findObject(ENTITY const* ent) {
	for (map<SpaceObject*, ENTITY*>::iterator i = _spaceObjectMap.begin();
		 i != _spaceObjectMap.end(); ++i) {
		if (i->second == ent) {
		 return i->first;
		}
	}

	return 0;
}

// Returns the entity corresponding to a given object
// If the ENTITY does not exist, returns 0.
ENTITY * GameManager::findEntity(SpaceObject* so) {
	map<SpaceObject*, ENTITY*>::iterator i = _spaceObjectMap.find(so);
	if (i != _spaceObjectMap.end()) {
		return i->second;
	}

	return 0;
}

// Returns the object corresponding to a given entity
// If the ENTITY does not exist, returns 0.
SpaceObject* GameManager::findNearestPlanet(SpaceObject const* so) {
	double min_d = numeric_limits<double>::infinity();
	SpaceObject* min_s = 0;
	for (std::vector<SpaceObject*>::iterator i = _planets.begin();
		 i != _planets.end(); ++i) {
			 double d = ((*i)->getPosition() - so->getPosition()).r2();
			 if (min_d > d) {
				min_d = d;
				min_s = (*i);
			 }
	}

	return min_s;
}

// finds the planet closest to the specified object.
SpaceObject* GameManager::findNearestEnemy(SpaceObject const* so)
{
	double min_d = numeric_limits<double>::infinity();
	SpaceObject* min_s = 0;
	Player const& p = *(so->getOwner());
	for (map<SpaceObject*, ENTITY*>::iterator i = _spaceObjectMap.begin();
		 i != _spaceObjectMap.end(); ++i) {
			 double d = (i->first->getPosition() - so->getPosition()).r2();
			 if (min_d > d && p.isEnemy(i->first)) {
				min_d = d;
				min_s = i->first;
			 }
	}

	return min_s;
}

//factory method for instantiating spaceship
Spaceship* GameManager::createSpaceship(SpaceshipType type)
{
	ENTITY* pEntity = 0;
	//instantiate a new Spaceship, instantiate new ENTITY
	switch(type) {
		case ST_CYSUS:
			pEntity = ent_create("cysus.mdl", ::vector(_VAR(0),_VAR(0),_VAR(0)), NULL);
			break;
		case ST_JONTH:
			pEntity = ent_create("jonth.mdl", ::vector(_VAR(0),_VAR(0),_VAR(0)), NULL);
			break;
		case ST_METHI:
			pEntity = ent_create("methi.mdl", ::vector(_VAR(0),_VAR(0),_VAR(0)), NULL);
			break;
		case ST_YATAI_VIII:
			pEntity = ent_create("yatai_viii.mdl", ::vector(_VAR(0),_VAR(0),_VAR(0)), NULL);
			break;
		default: 
			throw std::string("Invalid spaceship type.");
	}

	Spaceship* pShip = new Spaceship(type);
	_spaceObjectMap.insert(make_pair(pShip, pEntity));

	return pShip;
}


// notifies the gameManager that a space object has died
void GameManager::objectDied(SpaceObject * who)
{
	_deadObjects.push_back(who);
	_selectedObjects.erase(who);
}


// Adds a space object and the corresponding entity to the map of space objects.
// Used when loading a level
void GameManager::addSpaceship(Spaceship* so, ENTITY* ent)
{
	_spaceObjectMap.insert(make_pair(so, ent));
}


// delete a spaceship and the corresponding GameStudio entity.
void GameManager::removeSpaceship(Spaceship* so)
{
	ent_remove(_spaceObjectMap[so]);
	_spaceObjectMap.erase(so);
	so->getOwner()->removeSpaceship(so);
	delete so;
}

void GameManager::addPlanet(Planet* so, ENTITY* ent)
{
	_spaceObjectMap.insert(make_pair(so, ent));
	_planets.push_back(so);
}


// delete a planet and the corresponding GameStudio entity.
void GameManager::removePlanet(Planet* so)
{
	ent_remove(_spaceObjectMap[so]);
	_spaceObjectMap.erase(so);
	_planets.erase(find(_planets.begin(), _planets.end(), so));
	so->getOwner()->removePlanet(so);
	delete so;
}


// Adds a player
// Used when loading a level
void GameManager::addPlayer(Player* p)
{
	_players.push_back(p);
}


// getter for the player list
std::vector<Player*> const& GameManager::getPlayers() const
{
	return _players;
}




// Initialises the game engine
// Returns true on success, false otherwise
bool GameManager::initEngine()
{
	if(ev) {
		reset();
		engine_close();
		ev = 0;
	}
	
	ev = engine_open(0);
	if(!ev) return false; //acknex.dll not found

	srand(v(sys_seconds) + 60 * (v(sys_minutes) + 60 * v(sys_hours)));

	// Add additional model and level paths
	add_folder("../PlanetModels");
	add_folder("../SpaceshipModels");
	add_folder("../Levels");
	add_folder("../Graphics");
	add_folder("../Sound");

	MediaManager* mm = MediaManager::getInstance();
	ayesir = mm->getSound("ayeAyeSir.ogg");
	yescaptain = mm->getSound("yesCaptain.ogg");
	givemeabreak = mm->getSound("giveMeABreak.ogg");
	newordersforme = mm->getSound("newOrdersForMe.ogg");
	idunworkforu = mm->getSound("iDunWorkForU.ogg");


	SETV(fps_min, 30); //min framerate = 30
	SETV(video_mode, 8); //1024 x 768 screen resolution
	SETV(video_screen, 1); // 1 = fullscreen, 2 = windowed
	v(sky_color).red = 0;
	v(sky_color).green = 0;
	v(sky_color).blue = 0;

	// Set clipping ranges
	v(camera).clip_far = _VAR(1.5 * UNIVERSE_SIZE.x);

	//attach event handlers to game engine
	_inputManager = new InputManager;
	_inputManager->initEventHandling();

	// initialise the user interface
	_UIManager = UIManager::getInstance();
	UIManager::getInstance()->initialize();

	//create and display message board
	_messageBoard.createMessageBoard();

	_pCamera = new Camera;
	
	return true;
}


// This function contains the main game loop. It only returns when the
// Game finishes.
void GameManager::gameLoop()
{
	while (engine_frame()) {
		float timestep = _FLOAT(v(time_step)) / 16.0f;
		float totalTime = _FLOAT(v(total_ticks)) / 16.0f;

		// Update mouse status etc.
		_inputManager->update(timestep, totalTime);

		// Update panels, if they are displayed
		UIManager::getInstance()->update(timestep, totalTime);

		//loop thr all players and allow them to update itself
		for(std::vector<Player*>::iterator iter = _players.begin();
				iter!=_players.end();
				iter++) {
			(*iter)->update(timestep, totalTime);
		}

		//loop through all spaceships and planets, update them
		for(map<SpaceObject*, ENTITY*>::iterator iter = _spaceObjectMap.begin();
			iter!=_spaceObjectMap.end(); iter++) {
				iter->first->update(timestep, totalTime);

				// Update their decorations
				_unitDecorator->decorateUnit(iter->first, ev->camera);
		}

		// Draw a rectangle around selected Objects
		// This is merely a temporary solution...
		for (set<SpaceObject*>::iterator i = _selectedObjects.begin();
			i != _selectedObjects.end(); ++i) {
				VECTOR entPos;
				(*i)->getPosition().toGamestudio(&entPos);
				VECTOR * screenPos = vec_to_screen(&entPos, ev->camera);
				if (screenPos != 0) {
					drawRect(Vector(screenPos) - Vector(20, 20, 0),
						Vector(screenPos) + Vector(20, 20, 0),
						Vector(0, 0, 255));
				}
		}

		// delete all dead objects
		for (unsigned int i = 0; i < _deadObjects.size(); ++i) {
			Spaceship* ss = dynamic_cast<Spaceship*>(_deadObjects[i]);
			Planet* p = dynamic_cast<Planet*>(_deadObjects[i]);
			if (ss != 0) {
				removeSpaceship(ss);
			} else if (p != 0) {
				removePlanet(p);
			}

			// If the camera's target died, we need to set it to zero
			if (_deadObjects[i] == _pCamera->getTarget()) {
				_pCamera->setTarget(0);
			}
		}

		// notify every remaining object about the dead ones
		for(map<SpaceObject*, ENTITY*>::iterator iter = _spaceObjectMap.begin();
				iter!=_spaceObjectMap.end(); iter++) {
			for (unsigned int i = 0; i < _deadObjects.size(); ++i) {
				iter->first->somebodyDied(_deadObjects[i]);
			}
		}

		
		// clear the list of dead objects
		_deadObjects.clear();


		// If a single object is selected, draw the info window for it
		if (_selectedObjects.size() == 1) {
			UIManager::getInstance()->displayRightPanel(*(_selectedObjects.begin()));
		} else {
			UIManager::getInstance()->hideRightPanel();
		}

		// Update the camera based on keyboard input and target movement
		updateCamera(timestep, totalTime);

		// Copy all the changes into GameStudio.
		updateGameEngine();
	}
}


//collect data from game world then update values in game engine
void GameManager::updateGameEngine(void)
{
	//loop through all spaceships, update entities' position
	for(map<SpaceObject*, ENTITY*>::iterator iter = _spaceObjectMap.begin();
		iter!=_spaceObjectMap.end(); iter++) {
		//update position
		iter->first->getPosition().toGamestudio((VECTOR*)&(iter->second->x));
		
		//update angle
		Angle(iter->first->getAngle()).toGamestudio((ANGLE*)&(iter->second->pan));
	}

	//update the camera in game engine
	_pCamera->getPosition().toGamestudio((VECTOR*)&CAMERA.x);
	_pCamera->getAngle().toGamestudio((ANGLE*)&CAMERA.pan);
}

//scan keyboard for input and update camera position
void GameManager::updateCamera(float timestep, float totalTime)
{
	//gather input from keyboard
	int up = _INT(v(key_w));
	int down = _INT(v(key_s));
	int left = _INT(v(key_a));
	int right = _INT(v(key_d));
	int zoomIn = _INT(v(key_q)); //up arrow key
	int zoomOut = _INT(v(key_e)); //down arrow key
	if (_INT(v(mickey).z) > 0) {
		zoomIn += MOUSE_WHEEL_ZOOM_AMOUNT;
	} else if (_INT(v(mickey).z) < 0) {
		zoomOut += MOUSE_WHEEL_ZOOM_AMOUNT;
	}

	//calculate the new camera coordinates
	//the greater than 0 comparison helps avoid unnecessary computation
	if(up>0 || right>0 || down>0 || left>0) _pCamera->move(up, right, down, left, timestep);
	if(zoomIn>0 || zoomOut>0) _pCamera->zoom(zoomOut - zoomIn, timestep);
	_pCamera->update(timestep, totalTime);
}

// Resets the Game, i.e. destroys all the objects.
void GameManager::reset() {
	map<SpaceObject*, ENTITY*>::iterator i = _spaceObjectMap.begin();
	while(i != _spaceObjectMap.end()) {
		map<SpaceObject*, ENTITY*>::iterator old = i++; // use old value of i
		Spaceship* ss = dynamic_cast<Spaceship*>(old->first);
		Planet* p = dynamic_cast<Planet*>(old->first);
		if (ss != 0) {
			removeSpaceship(ss);
		} else if (p != 0) {
			removePlanet(p);
		}
	}

	for (std::vector<Player*>::iterator j = _players.begin();
		j != _players.end(); ++j) {
			delete *j;
	}
	_players.clear();
}

//append message to the message board on left side of screen
void GameManager::appendMessage(string const& message)
{
	_messageBoard.appendMessage(message);
}

void GameManager::drawRect(Vector corner1, Vector corner2, Vector color)
{
	VECTOR color2;
	color.toGamestudio(&color2);

	var xmin = _VAR(min(corner1.x, corner2.x));
	var xmax = _VAR(max(corner1.x, corner2.x));
	var ymin = _VAR(min(corner1.y, corner2.y));
	var ymax = _VAR(max(corner1.y, corner2.y));

	draw_line(::vector(xmin, ymin, 0), 0, _VAR(50));
	draw_line(::vector(xmax, ymin, 0), &color2, _VAR(50));
	draw_line(::vector(xmax, ymax, 0), &color2, _VAR(50));
	draw_line(::vector(xmin, ymax, 0), &color2, _VAR(50));
	draw_line(::vector(xmin, ymin, 0), &color2, _VAR(50));
	
}
void GameManager::addSelectedObject(SpaceObject* so){
	//add sound for 1st selected spaceship
	if(_selectedObjects.size()==0) {
		//make sure it is a spaceship not planet
		Spaceship* sp = dynamic_cast<Spaceship*>(so);
		if(sp!=0) {
			//make sure space ship belongs to player
			if(sp->getOwner()->getIsHumanPlayer()) {
				//play select sound
				if(sp->getType()==ST_METHI)
				{
					snd_play(ayesir,_VAR(1000),_VAR(0));
				}
				if(sp->getType()==ST_CYSUS)
				{					
					snd_play(yescaptain,_VAR(1000),_VAR(0));
				}
				if(sp->getType()==ST_YATAI_VIII)
				{				
					snd_play(givemeabreak,_VAR(1000),_VAR(0));
				}
				if(sp->getType()==ST_JONTH)
				{					
					snd_play(newordersforme,_VAR(1000),_VAR(0));
				}			
			}
			else
			{				
				snd_play(idunworkforu,_VAR(1000),_VAR(0));
			}
		}
	}
	
	_selectedObjects.insert(so);

	return;
}
